This study allows us to revisit/renew
- Regression modeling
- Properties of Least Squares/Fitting “a line”
- Multiple observation
Datasets for this study are
- The main file: gauge.txt
- Supplementary large-scale files: download the following folder Full Resolution Data.zip More information about the supplementary file can be found at http://iabp.apl.washington.edu/data.html as well as http://nsidc.org/data/G00791
Question
The aim of this lab is to provide a simple procedure for converting gain into density when the gauge is in operation. Keep in mind that the experiment was conducted by varying density and measuring the response in gain, but when the gauge is ultimately in use, the snow-pack density is to be estimated from the measured gain.
Setup
df <- read.table('gauge-1wb1wa6-2gpel41.txt', header=TRUE)
df <- df[order(df$density), ] # Sort from least to greatest density
m <- 9 # Number of distinct block densities
t <- 10 # Number of replicate measurements
#install.packages('L1pack')
library(L1pack) # Used for least absolute deviations regression line
Scenario 1: Fitting
Use the data to fit the gain, or a transformation of gain, to density. Try sketching the least squares line on a scatter plot.
- Do the residuals indicate any problems with the fit?
- If the densities of the polyethylene blocks are not reported exactly, how might this affect the fit?
- What if the blocks of polyethylene were not measured in random order (location)?
# Plot raw data
title <- 'Density vs. Gain'
x.axis <- expression('Density (g/cm'^3*')')
y.axis <- 'Gain'
x.range <- c(0, .7)
y.range <- c(2.5, 6)
plot(df, main=title, xlab=x.axis, ylab=y.axis, xlim=x.range)

# Take log transformation of response variable (gain)
y.log.axis = 'log(Gain)'
df.log = data.frame(df['density'], log(df['gain']))
plot(df.log, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)

# Average replicate measurements
df.log.avg = aggregate(list(gain=df.log$gain), by=list(density=df.log$density), FUN=mean)
plot(df.log.avg, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)

# Fit gain to density
cor(df.log.avg)
density gain
density 1.0000000 -0.9984469
gain -0.9984469 1.0000000
least.squares <- lm(gain~density, data=df.log.avg)
lad <- lad(gain~density, data=df.log.avg)
plot(df.log, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)
abline(least.squares, col='red')
abline(lad, col='blue')
legend('topright', legend=c('Least Squares Regression Line', 'Least Absolute Deviations Regression Line'), col=c('red', 'blue'), lty=1)

least.squares
Call:
lm(formula = gain ~ density, data = df.log.avg)
Coefficients:
(Intercept) density
5.997 -4.606
summary(least.squares)
Call:
lm(formula = gain ~ density, data = df.log.avg)
Residuals:
Min 1Q Median 3Q Max
-0.098150 -0.050047 -0.005898 0.061833 0.075681
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 5.99727 0.03889 154.22 1.27e-13 ***
density -4.60594 0.09714 -47.42 4.85e-10 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.06557 on 7 degrees of freedom
Multiple R-squared: 0.9969, Adjusted R-squared: 0.9965
F-statistic: 2248 on 1 and 7 DF, p-value: 4.854e-10
lad
Call:
lad(formula = gain ~ density, data = df.log.avg)
Converged in 4 iterations
Coefficients:
(Intercept) density
5.9850 -4.5935
Degrees of freedom: 9 total; 7 residual
Scale estimate: 0.06926379
summary(lad)
Call:
lad(formula = gain ~ density, data = df.log.avg)
Residuals:
Min 1Q Median 3Q Max
-0.08870 -0.04178 0.00000 0.07307 0.08038
Coefficients:
Estimate Std.Error Z value p-value
(Intercept) 5.9850 0.0581 103.0269 0.0000
density -4.5935 0.1451 -31.6550 0.0000
Degrees of freedom: 9 total; 7 residual
Scale estimate: 0.06926379
Log-likelihood: 11.90934 on 3 degrees of freedom
# Check conditions for linear regression: linearity, normality of residuals, and constant variability
least.squares.residuals <- data.frame(df.log['density'], df.log['gain'] - rep(predict(least.squares), each=10))
lad.residuals <- data.frame(df.log['density'], df.log['gain'] - rep(predict(lad), each=10))
title.residuals1 <- 'Residuals of Least Squares Regression Line'
title.residuals2 <- 'Residuals of Least Absolute Deviations Regression Line'
plot(least.squares.residuals$gain, main=title.residuals1, ylab=y.axis)
abline(0, 0, col='red')

plot(lad.residuals$gain, main=title.residuals2, ylab=y.axis)
abline(0, 0, col='blue')

num.bins <- 12
hist(least.squares.residuals$gain, breaks=num.bins, main=title.residuals1, xlab=y.axis, col='red')

hist(lad.residuals$gain, breaks=num.bins, main=title.residuals2, xlab=y.axis, col='blue')

qqnorm(least.squares.residuals$gain, main=paste('Normal Q-Q Plot with', title.residuals1), cex.main=1)
qqline(least.squares.residuals$gain, col='red')

qqnorm(lad.residuals$gain, main=paste('Normal Q-Q Plot with', title.residuals2), cex.main=1)
qqline(lad.residuals$gain, col='blue')

Scenario 2: Predicting
Ultimately we are interested in answering questions such as: Given a gain reading of 38.6, what is the density of the snow-pack? Given a gain reading of 426.7, what is the density of the snow-pack? These two numeric values, 38.6 and 426.7, were chosen because they are the average gains for the 0.508 and 0.001 densities, respectively.
- Develop a procedure for adding bands around your least squares line that can be used to make interval estimates for the snow-pack density from gain measurements. Keep in mind how the data were collected: several measurements of gain were taken for polyenythylene blocks of known density.
# Predictions
PredictLogGain <- function(density)
predict(least.squares, data.frame(density=density)) # Predict log(gain) using density
PredictDensityLeastSquares <- function(gain) {
intercept <- coef(least.squares)[[1]]
slope <- coef(least.squares)[[2]]
(log(gain) - intercept) / slope # Predict density using gain
}
PredictDensityLad <- function(gain) {
intercept <- coef(lad)[[1]]
slope <- coef(lad)[[2]]
(log(gain) - intercept) / slope # Predict density using gain
}
PredictDensityQuantile <- function(gain) {
intercept <- 0
slope <- 0
(log(gain) - intercept) / slope # Predict density using gain
}
# 95% prediction and confidence intervals of log(gain) using density
t <- qt(.975, df=m-2)
mean.density <- mean(df.log.avg$density)
summation <- sum((df.log.avg$density - mean.density) ^ 2)
s2 <- aggregate(list(variance=least.squares.residuals$gain), by=list(density=least.squares.residuals$density), FUN=var)
s.pooled <- sqrt(mean(s2$variance))
center.expr <- quote(center <- PredictLogGain(density))
ci.width.expr <- quote(width <- t * s.pooled * sqrt(1/m + (density-mean.density)^2 / summation))
pi.width.expr <- quote(width <- t * s.pooled * sqrt(1 + 1/m + (density-mean.density)^2 / summation))
LogGainCiLower <- function(density) {
eval(center.expr)
eval(ci.width.expr)
center - width
}
LogGainCiUpper <- function(density) {
eval(center.expr)
eval(ci.width.expr)
center + width
}
LogGainPiLower <- function(density) {
eval(center.expr)
eval(pi.width.expr)
center - width
}
LogGainPiUpper <- function(density) {
eval(center.expr)
eval(pi.width.expr)
center + width
}
# Add bands around least squares line
plot(df.log, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)
abline(least.squares, col='red')
ci.col <- 'purple'
pi.col <- 'blue'
symbol <- '-'
size <- 1.5
line.type <- 3
line.width <- 0.7
confidence.intervals <- data.frame(density=df.log.avg$density, lower=LogGainCiLower(df.log.avg$density), upper=LogGainCiUpper(df.log.avg$density))
points(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, pch=symbol, cex=size)
points(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, pch=symbol, cex=size)
lines(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, lty=line.type, lwd=line.width)
lines(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, lty=line.type, lwd=line.width)
prediction.intervals <- data.frame(density=df.log.avg$density, lower=LogGainPiLower(df.log.avg$density), upper=LogGainPiUpper(df.log.avg$density))
points(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, pch=symbol, cex=size)
points(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, pch=symbol, cex=size)
lines(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, lty=line.type, lwd=line.width)
lines(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, lty=line.type, lwd=line.width)
legend('topright', legend=c('Least Squares Regression Line', '95% Confidence Interval Bands', '95% Prediction Interval Bands'), col=c('red', ci.col, pi.col), lty=1)

# 95% prediction and confidence intervals of density using gain
end.points <- c(-1, 3) # Interval to search the root in
DensityCi <- function(gain) {
lower <- uniroot(function(density) log(gain) - LogGainCiLower(density), interval=end.points)[[1]]
upper <- uniroot(function(density) log(gain) - LogGainCiUpper(density), interval=end.points)[[1]]
c(lower, upper)
}
DensityPi <- function(gain) {
lower <- uniroot(function(density) log(gain) - LogGainPiLower(density), end.points)[[1]]
upper <- uniroot(function(density) log(gain) - LogGainPiUpper(density), end.points)[[1]]
c(lower, upper)
}
# Point and interval estimates for example gain readings
PredictDensityLeastSquares(38.6) # 38.6 is the average gain for 0.508 density
[1] 0.5089113
PredictDensityLad(38.6)
[1] 0.5076298
#PredictDensityQuantile(38.6)
DensityCi(38.6)
[1] 0.5011879 0.5169000
DensityPi(38.6)
[1] 0.4889568 0.5291323
PredictDensityLeastSquares(426.7) # 426.7 is the average gain for 0.001 density
[1] -0.01276954
PredictDensityLad(426.7)
[1] -0.01546807
#PredictDensityQuantile(38.6)
DensityCi(426.7)
[1] -0.024285866 -0.001769193
DensityPi(426.7)
[1] -0.034644666 0.008618629
Scenario 3: Cross-Validation
To check how well your procedure works, omit the set of measurements corresponding to the block of density 0.508, apply your “estimation”/calibration procedure to the remaining data, and provide an interval estimate for the density of a block with an average reading of 38.6. Where does the actual density fall in the interval? Try the same test, for the set of measurements at the 0.001 density.
for (omitted in c(0.508, 0.001)) {
# Omit measurements corresponding to the specified density
df.log.omitted = df.log[which(df.log['density'] != omitted), ]
df.log.avg.omitted <- df.log.avg[which(df.log.avg['density'] != omitted), ]
# Redo calculations using modified dataset
least.squares <- lm(gain~density, data=df.log.avg.omitted)
mean.density <- mean(df.log.avg.omitted$density)
summation <- sum((df.log.avg.omitted$density - mean.density) ^ 2)
s2 <- aggregate(list(variance=least.squares.residuals$gain), by=list(density=least.squares.residuals$density), FUN=var)
s.pooled <- sqrt(mean(s2$variance))
ci.width.expr <- quote(width <- t * s.pooled * sqrt(1/(m-1) + (density-mean.density)^2 / summation))
pi.width.expr <- quote(width <- t * s.pooled * sqrt(1 + 1/(m-1) + (density-mean.density)^2 / summation))
plot(df.log.omitted, main=title, xlab=x.axis, ylab=y.log.axis, xlim=x.range, ylim=y.range)
abline(least.squares, col='red')
ci.col <- 'purple'
pi.col <- 'blue'
symbol <- '-'
size <- 1.5
line.type <- 3
line.width <- 0.7
confidence.intervals <- data.frame(density=df.log.avg.omitted$density, lower=LogGainCiLower(df.log.avg.omitted$density), upper=LogGainCiUpper(df.log.avg.omitted$density))
points(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, pch=symbol, cex=size)
points(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, pch=symbol, cex=size)
lines(x=confidence.intervals$density, y=confidence.intervals$lower, col=ci.col, lty=line.type, lwd=line.width)
lines(x=confidence.intervals$density, y=confidence.intervals$upper, col=ci.col, lty=line.type, lwd=line.width)
prediction.intervals <- data.frame(density=df.log.avg.omitted$density, lower=LogGainPiLower(df.log.avg.omitted$density), upper=LogGainPiUpper(df.log.avg.omitted$density))
points(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, pch=symbol, cex=size)
points(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, pch=symbol, cex=size)
lines(x=prediction.intervals$density, y=prediction.intervals$lower, col=pi.col, lty=line.type, lwd=line.width)
lines(x=prediction.intervals$density, y=prediction.intervals$upper, col=pi.col, lty=line.type, lwd=line.width)
legend('topright', legend=c('Least Squares Regression Line', '95% Confidence Interval Bands', '95% Prediction Interval Bands'), col=c('red', ci.col, pi.col), lty=1)
print(PredictDensityLeastSquares(38.6)) # 38.6 is the average gain for 0.508 density
print(DensityCi(38.6))
print(DensityPi(38.6))
print(PredictDensityLeastSquares(426.7)) # 426.7 is the average gain for 0.001 density
print(DensityCi(426.7))
print(DensityPi(426.7))
}
[1] 0.5091927
[1] 0.5006695 0.5180406
[1] 0.4889184 0.5297925
[1] -0.0128045
[1] -0.024342164 -0.001790802
[1] -0.034760736 0.008598777

[1] 0.5092919
[1] 0.5014370 0.5174323
[1] 0.4890257 0.5298473
[1] -0.02051733
[1] -0.035344991 -0.006521825
[1] -0.044604850 0.002738127

Additional Scenario: Temperature, DOY, and Latitude.
Use the additional dataset to construct a model fitting temperature with DOY, latitude, and other reasonable features. Try sketching the least squares line on a scatter plot. We aim to investigate the relationship between temperature and the DOY, and its latitude.
# Check the correlation
data <- read.csv('Full Resolution Data/64506420.csv', header=TRUE)
data <- data[,c('Hour','DOY','POS_DOY','Lat','Lon','Ts','BP')]
# Drop the extreme outlier case
#data <- data[which(data$Ts>-200),]
data_matrix <- as.matrix(data)
# Correlation Matrix
corr_matrix <- cor(data_matrix)
corr_matrix
Hour DOY POS_DOY Lat Lon Ts BP
Hour 1.000000000 -0.006779489 -0.006775002 -0.007511024 0.01217860 0.008592576 0.02505819
DOY -0.006779489 1.000000000 0.999999958 0.903704617 -0.68012490 -0.906918658 -0.17232397
POS_DOY -0.006775002 0.999999958 1.000000000 0.903696909 -0.68013445 -0.906916964 -0.17230481
Lat -0.007511024 0.903704617 0.903696909 1.000000000 -0.59748962 -0.958835395 -0.29666821
Lon 0.012178596 -0.680124899 -0.680134450 -0.597489620 1.00000000 0.564136723 -0.02981279
Ts 0.008592576 -0.906918658 -0.906916964 -0.958835395 0.56413672 1.000000000 0.20223136
BP 0.025058188 -0.172323969 -0.172304805 -0.296668215 -0.02981279 0.202231363 1.00000000
# least squares line
fit<-lm(formula = Ts ~ DOY + Lat, data = data)
summary(fit)
Call:
lm(formula = Ts ~ DOY + Lat, data = data)
Residuals:
Min 1Q Median 3Q Max
-0.86025 -0.21975 -0.01511 0.22960 0.70988
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 40.0406586 0.5559051 72.03 <2e-16 ***
DOY -0.0098374 0.0006141 -16.02 <2e-16 ***
Lat -0.4913977 0.0089025 -55.20 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.3023 on 2066 degrees of freedom
Multiple R-squared: 0.9283, Adjusted R-squared: 0.9282
F-statistic: 1.337e+04 on 2 and 2066 DF, p-value: < 2.2e-16
ggplot(data,aes(x=DOY, y=Ts)) +
geom_point(color='#2980B9', size = 4) +
geom_smooth(method=lm, color='#2C3E50')

ggplot(data,aes(x=Lat, y=Ts)) +
geom_point(color='#2980B9', size = 4) +
geom_smooth(method=lm, color='#2C3E50')

title.residuals1 <- 'Residuals of Least Squares Regression Line'
plot(fit$residuals, main=title.residuals1)
abline(0, 0, col='red')

num.bins <- 10
hist(fit$residuals, breaks=num.bins, main=title.residuals1, xlab='Temperature', col='red')

qqnorm(fit$residuals, main=paste('Normal Q-Q Plot with', title.residuals1), cex.main=1)
qqline(fit$residuals, col='red')

# Attempt for Multiple Regressions
data$Midnights <- 0
data$Midnights[which(data$Hour<6)] <- 1
data$Mornings <- 0
data$Mornings[which(data$Hour>=6 & data$Hour<11)] <- 1
data$Noon <- 0
data$Noon[which(data$Hour>=11 & data$Hour<15)] <- 1
data$Afternoon <- 0
data$Afternoon[which(data$Hour>=15 & data$Hour<20)] <- 1
data$Nights <- 0
data$Nights[which(data$Hour>=20)] <- 1
LS0tCnRpdGxlOiAnQ0FTRSBTVFVEWSA0OicKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBzdHVkeSBhbGxvd3MgdXMgdG8gcmV2aXNpdC9yZW5ldwoKMS4gUmVncmVzc2lvbiBtb2RlbGluZwoyLiBQcm9wZXJ0aWVzIG9mIExlYXN0IFNxdWFyZXMvRml0dGluZyAiYSBsaW5lIgozLiBNdWx0aXBsZSBvYnNlcnZhdGlvbgoKRGF0YXNldHMgZm9yIHRoaXMgc3R1ZHkgYXJlCgoxLiBUaGUgbWFpbiBmaWxlOiBnYXVnZS50eHQKMi4gU3VwcGxlbWVudGFyeSBsYXJnZS1zY2FsZSBmaWxlczogZG93bmxvYWQgdGhlIGZvbGxvd2luZyBmb2xkZXIgRnVsbCBSZXNvbHV0aW9uIERhdGEuemlwIE1vcmUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHN1cHBsZW1lbnRhcnkgZmlsZSBjYW4gYmUgZm91bmQgYXQgaHR0cDovL2lhYnAuYXBsLndhc2hpbmd0b24uZWR1L2RhdGEuaHRtbCBhcyB3ZWxsIGFzIGh0dHA6Ly9uc2lkYy5vcmcvZGF0YS9HMDA3OTEKCgojIyBRdWVzdGlvbgpUaGUgYWltIG9mIHRoaXMgbGFiIGlzIHRvIHByb3ZpZGUgYSBzaW1wbGUgcHJvY2VkdXJlIGZvciBjb252ZXJ0aW5nIGdhaW4gaW50byBkZW5zaXR5IHdoZW4gdGhlIGdhdWdlIGlzIGluIG9wZXJhdGlvbi4gS2VlcCBpbiBtaW5kIHRoYXQgdGhlIGV4cGVyaW1lbnQgd2FzIGNvbmR1Y3RlZCBieSB2YXJ5aW5nIGRlbnNpdHkgYW5kIG1lYXN1cmluZyB0aGUgcmVzcG9uc2UgaW4gZ2FpbiwgYnV0IHdoZW4gdGhlIGdhdWdlIGlzIHVsdGltYXRlbHkgaW4gdXNlLCB0aGUgc25vdy1wYWNrIGRlbnNpdHkgaXMgdG8gYmUgZXN0aW1hdGVkIGZyb20gdGhlIG1lYXN1cmVkIGdhaW4uCgoKIyMgU2V0dXAKYGBge3J9CmRmIDwtIHJlYWQudGFibGUoJ2dhdWdlLTF3YjF3YTYtMmdwZWw0MS50eHQnLCBoZWFkZXI9VFJVRSkKZGYgPC0gZGZbb3JkZXIoZGYkZGVuc2l0eSksIF0gICMgU29ydCBmcm9tIGxlYXN0IHRvIGdyZWF0ZXN0IGRlbnNpdHkKCm0gPC0gOSAgIyBOdW1iZXIgb2YgZGlzdGluY3QgYmxvY2sgZGVuc2l0aWVzCnQgPC0gMTAgICMgTnVtYmVyIG9mIHJlcGxpY2F0ZSBtZWFzdXJlbWVudHMKCiNpbnN0YWxsLnBhY2thZ2VzKCdMMXBhY2snKQpsaWJyYXJ5KEwxcGFjaykgICMgVXNlZCBmb3IgbGVhc3QgYWJzb2x1dGUgZGV2aWF0aW9ucyByZWdyZXNzaW9uIGxpbmUKYGBgCgoKIyMgU2NlbmFyaW8gMTogRml0dGluZwpVc2UgdGhlIGRhdGEgdG8gZml0IHRoZSBnYWluLCBvciBhIHRyYW5zZm9ybWF0aW9uIG9mIGdhaW4sIHRvIGRlbnNpdHkuIFRyeSBza2V0Y2hpbmcgdGhlIGxlYXN0IHNxdWFyZXMgbGluZSBvbiBhIHNjYXR0ZXIgcGxvdC4KCiogRG8gdGhlIHJlc2lkdWFscyBpbmRpY2F0ZSBhbnkgcHJvYmxlbXMgd2l0aCB0aGUgZml0PwoqIElmIHRoZSBkZW5zaXRpZXMgb2YgdGhlIHBvbHlldGh5bGVuZSBibG9ja3MgYXJlIG5vdCByZXBvcnRlZCBleGFjdGx5LCBob3cgbWlnaHQgdGhpcyBhZmZlY3QgdGhlIGZpdD8KKiBXaGF0IGlmIHRoZSBibG9ja3Mgb2YgcG9seWV0aHlsZW5lIHdlcmUgbm90IG1lYXN1cmVkIGluIHJhbmRvbSBvcmRlciAobG9jYXRpb24pPwpgYGB7cn0KIyBQbG90IHJhdyBkYXRhCnRpdGxlIDwtICdEZW5zaXR5IHZzLiBHYWluJwp4LmF4aXMgPC0gZXhwcmVzc2lvbignRGVuc2l0eSAoZy9jbSdeMyonKScpCnkuYXhpcyA8LSAnR2FpbicKeC5yYW5nZSA8LSBjKDAsIC43KQp5LnJhbmdlIDwtIGMoMi41LCA2KQpwbG90KGRmLCBtYWluPXRpdGxlLCB4bGFiPXguYXhpcywgeWxhYj15LmF4aXMsIHhsaW09eC5yYW5nZSkKCgojIFRha2UgbG9nIHRyYW5zZm9ybWF0aW9uIG9mIHJlc3BvbnNlIHZhcmlhYmxlIChnYWluKQp5LmxvZy5heGlzID0gJ2xvZyhHYWluKScKZGYubG9nID0gZGF0YS5mcmFtZShkZlsnZGVuc2l0eSddLCBsb2coZGZbJ2dhaW4nXSkpCnBsb3QoZGYubG9nLCBtYWluPXRpdGxlLCB4bGFiPXguYXhpcywgeWxhYj15LmxvZy5heGlzLCB4bGltPXgucmFuZ2UsIHlsaW09eS5yYW5nZSkKCgojIEF2ZXJhZ2UgcmVwbGljYXRlIG1lYXN1cmVtZW50cwpkZi5sb2cuYXZnID0gYWdncmVnYXRlKGxpc3QoZ2Fpbj1kZi5sb2ckZ2FpbiksIGJ5PWxpc3QoZGVuc2l0eT1kZi5sb2ckZGVuc2l0eSksIEZVTj1tZWFuKQpwbG90KGRmLmxvZy5hdmcsIG1haW49dGl0bGUsIHhsYWI9eC5heGlzLCB5bGFiPXkubG9nLmF4aXMsIHhsaW09eC5yYW5nZSwgeWxpbT15LnJhbmdlKQoKCiMgRml0IGdhaW4gdG8gZGVuc2l0eQpjb3IoZGYubG9nLmF2ZykKCmxlYXN0LnNxdWFyZXMgPC0gbG0oZ2Fpbn5kZW5zaXR5LCBkYXRhPWRmLmxvZy5hdmcpCmxhZCA8LSBsYWQoZ2Fpbn5kZW5zaXR5LCBkYXRhPWRmLmxvZy5hdmcpCgpwbG90KGRmLmxvZywgbWFpbj10aXRsZSwgeGxhYj14LmF4aXMsIHlsYWI9eS5sb2cuYXhpcywgeGxpbT14LnJhbmdlLCB5bGltPXkucmFuZ2UpCmFibGluZShsZWFzdC5zcXVhcmVzLCBjb2w9J3JlZCcpCmFibGluZShsYWQsIGNvbD0nYmx1ZScpCmxlZ2VuZCgndG9wcmlnaHQnLCBsZWdlbmQ9YygnTGVhc3QgU3F1YXJlcyBSZWdyZXNzaW9uIExpbmUnLCAnTGVhc3QgQWJzb2x1dGUgRGV2aWF0aW9ucyBSZWdyZXNzaW9uIExpbmUnKSwgY29sPWMoJ3JlZCcsICdibHVlJyksIGx0eT0xKQoKbGVhc3Quc3F1YXJlcwpzdW1tYXJ5KGxlYXN0LnNxdWFyZXMpCmxhZApzdW1tYXJ5KGxhZCkKCgojIENoZWNrIGNvbmRpdGlvbnMgZm9yIGxpbmVhciByZWdyZXNzaW9uOiBsaW5lYXJpdHksIG5vcm1hbGl0eSBvZiByZXNpZHVhbHMsIGFuZCBjb25zdGFudCB2YXJpYWJpbGl0eQpsZWFzdC5zcXVhcmVzLnJlc2lkdWFscyA8LSBkYXRhLmZyYW1lKGRmLmxvZ1snZGVuc2l0eSddLCBkZi5sb2dbJ2dhaW4nXSAtIHJlcChwcmVkaWN0KGxlYXN0LnNxdWFyZXMpLCBlYWNoPTEwKSkKbGFkLnJlc2lkdWFscyA8LSBkYXRhLmZyYW1lKGRmLmxvZ1snZGVuc2l0eSddLCBkZi5sb2dbJ2dhaW4nXSAtIHJlcChwcmVkaWN0KGxhZCksIGVhY2g9MTApKQoKdGl0bGUucmVzaWR1YWxzMSA8LSAnUmVzaWR1YWxzIG9mIExlYXN0IFNxdWFyZXMgUmVncmVzc2lvbiBMaW5lJwp0aXRsZS5yZXNpZHVhbHMyIDwtICdSZXNpZHVhbHMgb2YgTGVhc3QgQWJzb2x1dGUgRGV2aWF0aW9ucyBSZWdyZXNzaW9uIExpbmUnCnBsb3QobGVhc3Quc3F1YXJlcy5yZXNpZHVhbHMkZ2FpbiwgbWFpbj10aXRsZS5yZXNpZHVhbHMxLCB5bGFiPXkuYXhpcykKYWJsaW5lKDAsIDAsIGNvbD0ncmVkJykKcGxvdChsYWQucmVzaWR1YWxzJGdhaW4sIG1haW49dGl0bGUucmVzaWR1YWxzMiwgeWxhYj15LmF4aXMpCmFibGluZSgwLCAwLCBjb2w9J2JsdWUnKQoKbnVtLmJpbnMgPC0gMTIKaGlzdChsZWFzdC5zcXVhcmVzLnJlc2lkdWFscyRnYWluLCBicmVha3M9bnVtLmJpbnMsIG1haW49dGl0bGUucmVzaWR1YWxzMSwgeGxhYj15LmF4aXMsIGNvbD0ncmVkJykKaGlzdChsYWQucmVzaWR1YWxzJGdhaW4sIGJyZWFrcz1udW0uYmlucywgbWFpbj10aXRsZS5yZXNpZHVhbHMyLCB4bGFiPXkuYXhpcywgY29sPSdibHVlJykKCnFxbm9ybShsZWFzdC5zcXVhcmVzLnJlc2lkdWFscyRnYWluLCBtYWluPXBhc3RlKCdOb3JtYWwgUS1RIFBsb3Qgd2l0aCcsIHRpdGxlLnJlc2lkdWFsczEpLCBjZXgubWFpbj0xKQpxcWxpbmUobGVhc3Quc3F1YXJlcy5yZXNpZHVhbHMkZ2FpbiwgY29sPSdyZWQnKQpxcW5vcm0obGFkLnJlc2lkdWFscyRnYWluLCBtYWluPXBhc3RlKCdOb3JtYWwgUS1RIFBsb3Qgd2l0aCcsIHRpdGxlLnJlc2lkdWFsczIpLCBjZXgubWFpbj0xKQpxcWxpbmUobGFkLnJlc2lkdWFscyRnYWluLCBjb2w9J2JsdWUnKQpgYGAKCgojIyBTY2VuYXJpbyAyOiBQcmVkaWN0aW5nClVsdGltYXRlbHkgd2UgYXJlIGludGVyZXN0ZWQgaW4gYW5zd2VyaW5nIHF1ZXN0aW9ucyBzdWNoIGFzOiBHaXZlbiBhIGdhaW4gcmVhZGluZyBvZiAzOC42LCB3aGF0IGlzIHRoZSBkZW5zaXR5IG9mIHRoZSBzbm93LXBhY2s/IEdpdmVuIGEgZ2FpbiByZWFkaW5nIG9mIDQyNi43LCB3aGF0IGlzIHRoZSBkZW5zaXR5IG9mIHRoZSBzbm93LXBhY2s/IFRoZXNlIHR3byBudW1lcmljIHZhbHVlcywgMzguNiBhbmQgNDI2LjcsIHdlcmUgY2hvc2VuIGJlY2F1c2UgdGhleSBhcmUgdGhlIGF2ZXJhZ2UgZ2FpbnMgZm9yIHRoZSAwLjUwOCBhbmQgMC4wMDEgZGVuc2l0aWVzLCByZXNwZWN0aXZlbHkuCgoqIERldmVsb3AgYSBwcm9jZWR1cmUgZm9yIGFkZGluZyBiYW5kcyBhcm91bmQgeW91ciBsZWFzdCBzcXVhcmVzIGxpbmUgdGhhdCBjYW4gYmUgdXNlZCB0byBtYWtlIGludGVydmFsIGVzdGltYXRlcyBmb3IgdGhlIHNub3ctcGFjayBkZW5zaXR5IGZyb20gZ2FpbiBtZWFzdXJlbWVudHMuIEtlZXAgaW4gbWluZCBob3cgdGhlIGRhdGEgd2VyZSBjb2xsZWN0ZWQ6IHNldmVyYWwgbWVhc3VyZW1lbnRzIG9mIGdhaW4gd2VyZSB0YWtlbiBmb3IgcG9seWVueXRoeWxlbmUgYmxvY2tzIG9mIGtub3duIGRlbnNpdHkuCmBgYHtyIGZpZy5hc3A9MiwgZmlnLndpZHRoPTV9CiMgUHJlZGljdGlvbnMKUHJlZGljdExvZ0dhaW4gPC0gZnVuY3Rpb24oZGVuc2l0eSkKICBwcmVkaWN0KGxlYXN0LnNxdWFyZXMsIGRhdGEuZnJhbWUoZGVuc2l0eT1kZW5zaXR5KSkgICMgUHJlZGljdCBsb2coZ2FpbikgdXNpbmcgZGVuc2l0eQoKUHJlZGljdERlbnNpdHlMZWFzdFNxdWFyZXMgPC0gZnVuY3Rpb24oZ2FpbikgewogIGludGVyY2VwdCA8LSBjb2VmKGxlYXN0LnNxdWFyZXMpW1sxXV0KICBzbG9wZSA8LSBjb2VmKGxlYXN0LnNxdWFyZXMpW1syXV0KICAobG9nKGdhaW4pIC0gaW50ZXJjZXB0KSAvIHNsb3BlICAjIFByZWRpY3QgZGVuc2l0eSB1c2luZyBnYWluCn0KClByZWRpY3REZW5zaXR5TGFkIDwtIGZ1bmN0aW9uKGdhaW4pIHsKICBpbnRlcmNlcHQgPC0gY29lZihsYWQpW1sxXV0KICBzbG9wZSA8LSBjb2VmKGxhZClbWzJdXQogIChsb2coZ2FpbikgLSBpbnRlcmNlcHQpIC8gc2xvcGUgICMgUHJlZGljdCBkZW5zaXR5IHVzaW5nIGdhaW4KfQoKUHJlZGljdERlbnNpdHlRdWFudGlsZSA8LSBmdW5jdGlvbihnYWluKSB7CiAgaW50ZXJjZXB0IDwtIDAKICBzbG9wZSA8LSAwCiAgKGxvZyhnYWluKSAtIGludGVyY2VwdCkgLyBzbG9wZSAgIyBQcmVkaWN0IGRlbnNpdHkgdXNpbmcgZ2Fpbgp9CgoKIyA5NSUgcHJlZGljdGlvbiBhbmQgY29uZmlkZW5jZSBpbnRlcnZhbHMgb2YgbG9nKGdhaW4pIHVzaW5nIGRlbnNpdHkKdCA8LSBxdCguOTc1LCBkZj1tLTIpCm1lYW4uZGVuc2l0eSA8LSBtZWFuKGRmLmxvZy5hdmckZGVuc2l0eSkKc3VtbWF0aW9uIDwtIHN1bSgoZGYubG9nLmF2ZyRkZW5zaXR5IC0gbWVhbi5kZW5zaXR5KSBeIDIpCgpzMiA8LSBhZ2dyZWdhdGUobGlzdCh2YXJpYW5jZT1sZWFzdC5zcXVhcmVzLnJlc2lkdWFscyRnYWluKSwgYnk9bGlzdChkZW5zaXR5PWxlYXN0LnNxdWFyZXMucmVzaWR1YWxzJGRlbnNpdHkpLCBGVU49dmFyKQpzLnBvb2xlZCA8LSBzcXJ0KG1lYW4oczIkdmFyaWFuY2UpKQoKY2VudGVyLmV4cHIgPC0gcXVvdGUoY2VudGVyIDwtIFByZWRpY3RMb2dHYWluKGRlbnNpdHkpKQpjaS53aWR0aC5leHByIDwtIHF1b3RlKHdpZHRoIDwtIHQgKiBzLnBvb2xlZCAqIHNxcnQoMS9tICsgKGRlbnNpdHktbWVhbi5kZW5zaXR5KV4yIC8gc3VtbWF0aW9uKSkKcGkud2lkdGguZXhwciA8LSBxdW90ZSh3aWR0aCA8LSB0ICogcy5wb29sZWQgKiBzcXJ0KDEgKyAxL20gKyAoZGVuc2l0eS1tZWFuLmRlbnNpdHkpXjIgLyBzdW1tYXRpb24pKQoKTG9nR2FpbkNpTG93ZXIgPC0gZnVuY3Rpb24oZGVuc2l0eSkgewogIGV2YWwoY2VudGVyLmV4cHIpCiAgZXZhbChjaS53aWR0aC5leHByKQogIGNlbnRlciAtIHdpZHRoCn0KCkxvZ0dhaW5DaVVwcGVyIDwtIGZ1bmN0aW9uKGRlbnNpdHkpIHsKICBldmFsKGNlbnRlci5leHByKQogIGV2YWwoY2kud2lkdGguZXhwcikKICBjZW50ZXIgKyB3aWR0aAp9CgpMb2dHYWluUGlMb3dlciA8LSBmdW5jdGlvbihkZW5zaXR5KSB7CiAgZXZhbChjZW50ZXIuZXhwcikKICBldmFsKHBpLndpZHRoLmV4cHIpCiAgY2VudGVyIC0gd2lkdGgKfQoKTG9nR2FpblBpVXBwZXIgPC0gZnVuY3Rpb24oZGVuc2l0eSkgewogIGV2YWwoY2VudGVyLmV4cHIpCiAgZXZhbChwaS53aWR0aC5leHByKQogIGNlbnRlciArIHdpZHRoCn0KCgojIEFkZCBiYW5kcyBhcm91bmQgbGVhc3Qgc3F1YXJlcyBsaW5lCnBsb3QoZGYubG9nLCBtYWluPXRpdGxlLCB4bGFiPXguYXhpcywgeWxhYj15LmxvZy5heGlzLCB4bGltPXgucmFuZ2UsIHlsaW09eS5yYW5nZSkKYWJsaW5lKGxlYXN0LnNxdWFyZXMsIGNvbD0ncmVkJykKCmNpLmNvbCA8LSAncHVycGxlJwpwaS5jb2wgPC0gJ2JsdWUnCnN5bWJvbCA8LSAnLScKc2l6ZSA8LSAxLjUKbGluZS50eXBlIDwtIDMKbGluZS53aWR0aCA8LSAwLjcKCmNvbmZpZGVuY2UuaW50ZXJ2YWxzIDwtIGRhdGEuZnJhbWUoZGVuc2l0eT1kZi5sb2cuYXZnJGRlbnNpdHksIGxvd2VyPUxvZ0dhaW5DaUxvd2VyKGRmLmxvZy5hdmckZGVuc2l0eSksIHVwcGVyPUxvZ0dhaW5DaVVwcGVyKGRmLmxvZy5hdmckZGVuc2l0eSkpCnBvaW50cyh4PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGRlbnNpdHksIHk9Y29uZmlkZW5jZS5pbnRlcnZhbHMkbG93ZXIsIGNvbD1jaS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQpwb2ludHMoeD1jb25maWRlbmNlLmludGVydmFscyRkZW5zaXR5LCB5PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJHVwcGVyLCBjb2w9Y2kuY29sLCBwY2g9c3ltYm9sLCBjZXg9c2l6ZSkKbGluZXMoeD1jb25maWRlbmNlLmludGVydmFscyRkZW5zaXR5LCB5PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGxvd2VyLCBjb2w9Y2kuY29sLCBsdHk9bGluZS50eXBlLCBsd2Q9bGluZS53aWR0aCkKbGluZXMoeD1jb25maWRlbmNlLmludGVydmFscyRkZW5zaXR5LCB5PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJHVwcGVyLCBjb2w9Y2kuY29sLCBsdHk9bGluZS50eXBlLCBsd2Q9bGluZS53aWR0aCkKCnByZWRpY3Rpb24uaW50ZXJ2YWxzIDwtIGRhdGEuZnJhbWUoZGVuc2l0eT1kZi5sb2cuYXZnJGRlbnNpdHksIGxvd2VyPUxvZ0dhaW5QaUxvd2VyKGRmLmxvZy5hdmckZGVuc2l0eSksIHVwcGVyPUxvZ0dhaW5QaVVwcGVyKGRmLmxvZy5hdmckZGVuc2l0eSkpCnBvaW50cyh4PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGRlbnNpdHksIHk9cHJlZGljdGlvbi5pbnRlcnZhbHMkbG93ZXIsIGNvbD1waS5jb2wsIHBjaD1zeW1ib2wsIGNleD1zaXplKQpwb2ludHMoeD1wcmVkaWN0aW9uLmludGVydmFscyRkZW5zaXR5LCB5PXByZWRpY3Rpb24uaW50ZXJ2YWxzJHVwcGVyLCBjb2w9cGkuY29sLCBwY2g9c3ltYm9sLCBjZXg9c2l6ZSkKbGluZXMoeD1wcmVkaWN0aW9uLmludGVydmFscyRkZW5zaXR5LCB5PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGxvd2VyLCBjb2w9cGkuY29sLCBsdHk9bGluZS50eXBlLCBsd2Q9bGluZS53aWR0aCkKbGluZXMoeD1wcmVkaWN0aW9uLmludGVydmFscyRkZW5zaXR5LCB5PXByZWRpY3Rpb24uaW50ZXJ2YWxzJHVwcGVyLCBjb2w9cGkuY29sLCBsdHk9bGluZS50eXBlLCBsd2Q9bGluZS53aWR0aCkKCmxlZ2VuZCgndG9wcmlnaHQnLCBsZWdlbmQ9YygnTGVhc3QgU3F1YXJlcyBSZWdyZXNzaW9uIExpbmUnLCAnOTUlIENvbmZpZGVuY2UgSW50ZXJ2YWwgQmFuZHMnLCAnOTUlIFByZWRpY3Rpb24gSW50ZXJ2YWwgQmFuZHMnKSwgY29sPWMoJ3JlZCcsIGNpLmNvbCwgcGkuY29sKSwgbHR5PTEpCgoKIyA5NSUgcHJlZGljdGlvbiBhbmQgY29uZmlkZW5jZSBpbnRlcnZhbHMgb2YgZGVuc2l0eSB1c2luZyBnYWluCmVuZC5wb2ludHMgPC0gYygtMSwgMykgICMgSW50ZXJ2YWwgdG8gc2VhcmNoIHRoZSByb290IGluCgpEZW5zaXR5Q2kgPC0gZnVuY3Rpb24oZ2FpbikgewogIGxvd2VyIDwtIHVuaXJvb3QoZnVuY3Rpb24oZGVuc2l0eSkgbG9nKGdhaW4pIC0gTG9nR2FpbkNpTG93ZXIoZGVuc2l0eSksIGludGVydmFsPWVuZC5wb2ludHMpW1sxXV0KICB1cHBlciA8LSB1bmlyb290KGZ1bmN0aW9uKGRlbnNpdHkpIGxvZyhnYWluKSAtIExvZ0dhaW5DaVVwcGVyKGRlbnNpdHkpLCBpbnRlcnZhbD1lbmQucG9pbnRzKVtbMV1dCiAgYyhsb3dlciwgdXBwZXIpCn0KCkRlbnNpdHlQaSA8LSBmdW5jdGlvbihnYWluKSB7CiAgbG93ZXIgPC0gdW5pcm9vdChmdW5jdGlvbihkZW5zaXR5KSBsb2coZ2FpbikgLSBMb2dHYWluUGlMb3dlcihkZW5zaXR5KSwgZW5kLnBvaW50cylbWzFdXQogIHVwcGVyIDwtIHVuaXJvb3QoZnVuY3Rpb24oZGVuc2l0eSkgbG9nKGdhaW4pIC0gTG9nR2FpblBpVXBwZXIoZGVuc2l0eSksIGVuZC5wb2ludHMpW1sxXV0KICBjKGxvd2VyLCB1cHBlcikKfQoKCiMgUG9pbnQgYW5kIGludGVydmFsIGVzdGltYXRlcyBmb3IgZXhhbXBsZSBnYWluIHJlYWRpbmdzClByZWRpY3REZW5zaXR5TGVhc3RTcXVhcmVzKDM4LjYpICAjIDM4LjYgaXMgdGhlIGF2ZXJhZ2UgZ2FpbiBmb3IgMC41MDggZGVuc2l0eQpQcmVkaWN0RGVuc2l0eUxhZCgzOC42KQojUHJlZGljdERlbnNpdHlRdWFudGlsZSgzOC42KQpEZW5zaXR5Q2koMzguNikKRGVuc2l0eVBpKDM4LjYpCgpQcmVkaWN0RGVuc2l0eUxlYXN0U3F1YXJlcyg0MjYuNykgICMgNDI2LjcgaXMgdGhlIGF2ZXJhZ2UgZ2FpbiBmb3IgMC4wMDEgZGVuc2l0eQpQcmVkaWN0RGVuc2l0eUxhZCg0MjYuNykKI1ByZWRpY3REZW5zaXR5UXVhbnRpbGUoMzguNikKRGVuc2l0eUNpKDQyNi43KQpEZW5zaXR5UGkoNDI2LjcpCmBgYAoKCiMjIFNjZW5hcmlvIDM6IENyb3NzLVZhbGlkYXRpb24KVG8gY2hlY2sgaG93IHdlbGwgeW91ciBwcm9jZWR1cmUgd29ya3MsIG9taXQgdGhlIHNldCBvZiBtZWFzdXJlbWVudHMgY29ycmVzcG9uZGluZyB0byB0aGUgYmxvY2sgb2YgZGVuc2l0eSAwLjUwOCwgYXBwbHkgeW91ciAiZXN0aW1hdGlvbiIvY2FsaWJyYXRpb24gcHJvY2VkdXJlIHRvIHRoZSByZW1haW5pbmcgZGF0YSwgYW5kIHByb3ZpZGUgYW4gaW50ZXJ2YWwgZXN0aW1hdGUgZm9yIHRoZSBkZW5zaXR5IG9mIGEgYmxvY2sgd2l0aCBhbiBhdmVyYWdlIHJlYWRpbmcgb2YgMzguNi4gV2hlcmUgZG9lcyB0aGUgYWN0dWFsIGRlbnNpdHkgZmFsbCBpbiB0aGUgaW50ZXJ2YWw/IFRyeSB0aGUgc2FtZSB0ZXN0LCBmb3IgdGhlIHNldCBvZiBtZWFzdXJlbWVudHMgYXQgdGhlIDAuMDAxIGRlbnNpdHkuCmBgYHtyIGZpZy5hc3A9MiwgZmlnLndpZHRoPTV9CmZvciAob21pdHRlZCBpbiBjKDAuNTA4LCAwLjAwMSkpIHsKICAjIE9taXQgbWVhc3VyZW1lbnRzIGNvcnJlc3BvbmRpbmcgdG8gdGhlIHNwZWNpZmllZCBkZW5zaXR5CiAgZGYubG9nLm9taXR0ZWQgPSBkZi5sb2dbd2hpY2goZGYubG9nWydkZW5zaXR5J10gIT0gb21pdHRlZCksIF0KICBkZi5sb2cuYXZnLm9taXR0ZWQgPC0gZGYubG9nLmF2Z1t3aGljaChkZi5sb2cuYXZnWydkZW5zaXR5J10gIT0gb21pdHRlZCksIF0KICAKICAKICAjIFJlZG8gY2FsY3VsYXRpb25zIHVzaW5nIG1vZGlmaWVkIGRhdGFzZXQKICBsZWFzdC5zcXVhcmVzIDwtIGxtKGdhaW5+ZGVuc2l0eSwgZGF0YT1kZi5sb2cuYXZnLm9taXR0ZWQpCiAgCiAgbWVhbi5kZW5zaXR5IDwtIG1lYW4oZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHkpCiAgc3VtbWF0aW9uIDwtIHN1bSgoZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHkgLSBtZWFuLmRlbnNpdHkpIF4gMikKICAKICBzMiA8LSBhZ2dyZWdhdGUobGlzdCh2YXJpYW5jZT1sZWFzdC5zcXVhcmVzLnJlc2lkdWFscyRnYWluKSwgYnk9bGlzdChkZW5zaXR5PWxlYXN0LnNxdWFyZXMucmVzaWR1YWxzJGRlbnNpdHkpLCBGVU49dmFyKQogIHMucG9vbGVkIDwtIHNxcnQobWVhbihzMiR2YXJpYW5jZSkpCiAgCiAgY2kud2lkdGguZXhwciA8LSBxdW90ZSh3aWR0aCA8LSB0ICogcy5wb29sZWQgKiBzcXJ0KDEvKG0tMSkgKyAoZGVuc2l0eS1tZWFuLmRlbnNpdHkpXjIgLyBzdW1tYXRpb24pKQogIHBpLndpZHRoLmV4cHIgPC0gcXVvdGUod2lkdGggPC0gdCAqIHMucG9vbGVkICogc3FydCgxICsgMS8obS0xKSArIChkZW5zaXR5LW1lYW4uZGVuc2l0eSleMiAvIHN1bW1hdGlvbikpCiAgCiAgcGxvdChkZi5sb2cub21pdHRlZCwgbWFpbj10aXRsZSwgeGxhYj14LmF4aXMsIHlsYWI9eS5sb2cuYXhpcywgeGxpbT14LnJhbmdlLCB5bGltPXkucmFuZ2UpCiAgYWJsaW5lKGxlYXN0LnNxdWFyZXMsIGNvbD0ncmVkJykKICAKICBjaS5jb2wgPC0gJ3B1cnBsZScKICBwaS5jb2wgPC0gJ2JsdWUnCiAgc3ltYm9sIDwtICctJwogIHNpemUgPC0gMS41CiAgbGluZS50eXBlIDwtIDMKICBsaW5lLndpZHRoIDwtIDAuNwogIAogIGNvbmZpZGVuY2UuaW50ZXJ2YWxzIDwtIGRhdGEuZnJhbWUoZGVuc2l0eT1kZi5sb2cuYXZnLm9taXR0ZWQkZGVuc2l0eSwgbG93ZXI9TG9nR2FpbkNpTG93ZXIoZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHkpLCB1cHBlcj1Mb2dHYWluQ2lVcHBlcihkZi5sb2cuYXZnLm9taXR0ZWQkZGVuc2l0eSkpCiAgcG9pbnRzKHg9Y29uZmlkZW5jZS5pbnRlcnZhbHMkZGVuc2l0eSwgeT1jb25maWRlbmNlLmludGVydmFscyRsb3dlciwgY29sPWNpLmNvbCwgcGNoPXN5bWJvbCwgY2V4PXNpemUpCiAgcG9pbnRzKHg9Y29uZmlkZW5jZS5pbnRlcnZhbHMkZGVuc2l0eSwgeT1jb25maWRlbmNlLmludGVydmFscyR1cHBlciwgY29sPWNpLmNvbCwgcGNoPXN5bWJvbCwgY2V4PXNpemUpCiAgbGluZXMoeD1jb25maWRlbmNlLmludGVydmFscyRkZW5zaXR5LCB5PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGxvd2VyLCBjb2w9Y2kuY29sLCBsdHk9bGluZS50eXBlLCBsd2Q9bGluZS53aWR0aCkKICBsaW5lcyh4PWNvbmZpZGVuY2UuaW50ZXJ2YWxzJGRlbnNpdHksIHk9Y29uZmlkZW5jZS5pbnRlcnZhbHMkdXBwZXIsIGNvbD1jaS5jb2wsIGx0eT1saW5lLnR5cGUsIGx3ZD1saW5lLndpZHRoKQogIAogIHByZWRpY3Rpb24uaW50ZXJ2YWxzIDwtIGRhdGEuZnJhbWUoZGVuc2l0eT1kZi5sb2cuYXZnLm9taXR0ZWQkZGVuc2l0eSwgbG93ZXI9TG9nR2FpblBpTG93ZXIoZGYubG9nLmF2Zy5vbWl0dGVkJGRlbnNpdHkpLCB1cHBlcj1Mb2dHYWluUGlVcHBlcihkZi5sb2cuYXZnLm9taXR0ZWQkZGVuc2l0eSkpCiAgcG9pbnRzKHg9cHJlZGljdGlvbi5pbnRlcnZhbHMkZGVuc2l0eSwgeT1wcmVkaWN0aW9uLmludGVydmFscyRsb3dlciwgY29sPXBpLmNvbCwgcGNoPXN5bWJvbCwgY2V4PXNpemUpCiAgcG9pbnRzKHg9cHJlZGljdGlvbi5pbnRlcnZhbHMkZGVuc2l0eSwgeT1wcmVkaWN0aW9uLmludGVydmFscyR1cHBlciwgY29sPXBpLmNvbCwgcGNoPXN5bWJvbCwgY2V4PXNpemUpCiAgbGluZXMoeD1wcmVkaWN0aW9uLmludGVydmFscyRkZW5zaXR5LCB5PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGxvd2VyLCBjb2w9cGkuY29sLCBsdHk9bGluZS50eXBlLCBsd2Q9bGluZS53aWR0aCkKICBsaW5lcyh4PXByZWRpY3Rpb24uaW50ZXJ2YWxzJGRlbnNpdHksIHk9cHJlZGljdGlvbi5pbnRlcnZhbHMkdXBwZXIsIGNvbD1waS5jb2wsIGx0eT1saW5lLnR5cGUsIGx3ZD1saW5lLndpZHRoKQogIAogIGxlZ2VuZCgndG9wcmlnaHQnLCBsZWdlbmQ9YygnTGVhc3QgU3F1YXJlcyBSZWdyZXNzaW9uIExpbmUnLCAnOTUlIENvbmZpZGVuY2UgSW50ZXJ2YWwgQmFuZHMnLCAnOTUlIFByZWRpY3Rpb24gSW50ZXJ2YWwgQmFuZHMnKSwgY29sPWMoJ3JlZCcsIGNpLmNvbCwgcGkuY29sKSwgbHR5PTEpCiAgCiAgcHJpbnQoUHJlZGljdERlbnNpdHlMZWFzdFNxdWFyZXMoMzguNikpICAjIDM4LjYgaXMgdGhlIGF2ZXJhZ2UgZ2FpbiBmb3IgMC41MDggZGVuc2l0eQogIHByaW50KERlbnNpdHlDaSgzOC42KSkKICBwcmludChEZW5zaXR5UGkoMzguNikpCiAgCiAgcHJpbnQoUHJlZGljdERlbnNpdHlMZWFzdFNxdWFyZXMoNDI2LjcpKSAgIyA0MjYuNyBpcyB0aGUgYXZlcmFnZSBnYWluIGZvciAwLjAwMSBkZW5zaXR5CiAgcHJpbnQoRGVuc2l0eUNpKDQyNi43KSkKICBwcmludChEZW5zaXR5UGkoNDI2LjcpKQp9CmBgYAoKCiMjIEFkZGl0aW9uYWwgU2NlbmFyaW86IFRlbXBlcmF0dXJlLCBET1ksIGFuZCBMYXRpdHVkZS4KVXNlIHRoZSBhZGRpdGlvbmFsIGRhdGFzZXQgdG8gY29uc3RydWN0IGEgbW9kZWwgZml0dGluZyB0ZW1wZXJhdHVyZSB3aXRoIERPWSwgbGF0aXR1ZGUsIGFuZCBvdGhlciByZWFzb25hYmxlIGZlYXR1cmVzLiBUcnkgc2tldGNoaW5nIHRoZSBsZWFzdCBzcXVhcmVzIGxpbmUgb24gYSBzY2F0dGVyIHBsb3QuIFdlIGFpbSB0byBpbnZlc3RpZ2F0ZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGVtcGVyYXR1cmUgYW5kIHRoZSBET1ksIGFuZCBpdHMgbGF0aXR1ZGUuCmBgYHtyfQojIENoZWNrIHRoZSBjb3JyZWxhdGlvbgpkYXRhIDwtIHJlYWQuY3N2KCdGdWxsIFJlc29sdXRpb24gRGF0YS82NDUwNjQyMC5jc3YnLCBoZWFkZXI9VFJVRSkKZGF0YSA8LSBkYXRhWyxjKCdIb3VyJywnRE9ZJywnUE9TX0RPWScsJ0xhdCcsJ0xvbicsJ1RzJywnQlAnKV0KCiMgRHJvcCB0aGUgZXh0cmVtZSBvdXRsaWVyIGNhc2UKI2RhdGEgPC0gZGF0YVt3aGljaChkYXRhJFRzPi0yMDApLF0KZGF0YV9tYXRyaXggPC0gYXMubWF0cml4KGRhdGEpCgojIENvcnJlbGF0aW9uIE1hdHJpeApjb3JyX21hdHJpeCA8LSBjb3IoZGF0YV9tYXRyaXgpCmNvcnJfbWF0cml4CmBgYAoKYGBge3J9CiMgbGVhc3Qgc3F1YXJlcyBsaW5lCmZpdDwtbG0oZm9ybXVsYSA9IFRzIH4gRE9ZICsgTGF0LCBkYXRhID0gZGF0YSkKc3VtbWFyeShmaXQpCgpnZ3Bsb3QoZGF0YSxhZXMoeD1ET1ksIHk9VHMpKSArIAogIGdlb21fcG9pbnQoY29sb3I9JyMyOTgwQjknLCBzaXplID0gNCkgKyAKICBnZW9tX3Ntb290aChtZXRob2Q9bG0sIGNvbG9yPScjMkMzRTUwJykKCmdncGxvdChkYXRhLGFlcyh4PUxhdCwgeT1UcykpICsgCiAgZ2VvbV9wb2ludChjb2xvcj0nIzI5ODBCOScsIHNpemUgPSA0KSArIAogIGdlb21fc21vb3RoKG1ldGhvZD1sbSwgY29sb3I9JyMyQzNFNTAnKQoKdGl0bGUucmVzaWR1YWxzMSA8LSAnUmVzaWR1YWxzIG9mIExlYXN0IFNxdWFyZXMgUmVncmVzc2lvbiBMaW5lJwpwbG90KGZpdCRyZXNpZHVhbHMsIG1haW49dGl0bGUucmVzaWR1YWxzMSkKYWJsaW5lKDAsIDAsIGNvbD0ncmVkJykKCm51bS5iaW5zIDwtIDEwCmhpc3QoZml0JHJlc2lkdWFscywgYnJlYWtzPW51bS5iaW5zLCBtYWluPXRpdGxlLnJlc2lkdWFsczEsIHhsYWI9J1RlbXBlcmF0dXJlJywgY29sPSdyZWQnKQoKcXFub3JtKGZpdCRyZXNpZHVhbHMsIG1haW49cGFzdGUoJ05vcm1hbCBRLVEgUGxvdCB3aXRoJywgdGl0bGUucmVzaWR1YWxzMSksIGNleC5tYWluPTEpCnFxbGluZShmaXQkcmVzaWR1YWxzLCBjb2w9J3JlZCcpCmBgYAoKYGBge3J9CiMgQXR0ZW1wdCBmb3IgTXVsdGlwbGUgUmVncmVzc2lvbnMKZGF0YSRNaWRuaWdodHMgPC0gMApkYXRhJE1pZG5pZ2h0c1t3aGljaChkYXRhJEhvdXI8NildIDwtIDEKZGF0YSRNb3JuaW5ncyA8LSAwCmRhdGEkTW9ybmluZ3Nbd2hpY2goZGF0YSRIb3VyPj02ICYgZGF0YSRIb3VyPDExKV0gPC0gMSAKZGF0YSROb29uIDwtIDAKZGF0YSROb29uW3doaWNoKGRhdGEkSG91cj49MTEgJiBkYXRhJEhvdXI8MTUpXSA8LSAxCmRhdGEkQWZ0ZXJub29uIDwtIDAKZGF0YSRBZnRlcm5vb25bd2hpY2goZGF0YSRIb3VyPj0xNSAmIGRhdGEkSG91cjwyMCldIDwtIDEKZGF0YSROaWdodHMgPC0gMApkYXRhJE5pZ2h0c1t3aGljaChkYXRhJEhvdXI+PTIwKV0gPC0gMQpgYGA=